package ServerCommands;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;

import ClientCommands.ClientAcroCommand;
import ClientCommands.ClientAnswerCommand;
import ClientCommands.ClientChatCommand;
import ClientCommands.ClientScoreCommand;

public class Room implements Runnable {

	LinkedList<PlayerThread> currentPlayers;
	Random generator = new Random();
	int roundCount;
	String password;
	String roomName;
	ServerSocket sSocket;
	Socket socket;
	int gamePosition;
	int port;
	boolean keepPlaying;

	public Room(int port) {
		this.port = port;
		keepPlaying = true;
		gamePosition = 0;
	}

	public void run() {
		
		// this thread will perpetually wait for new players entering the room. Upon a new player entering, a new PlayerThread is created for that player.
		new NewPlayerThread(port).start();

		roundCount = 1; // keeping track of the number of rounds
		String acro; // the acronym will be stored in this

		// Each player in the room has their own thread. In this thread their votes, answers, username, and sockets are stored
		currentPlayers = new LinkedList<PlayerThread>(); 

		// Do not start a room until their are at least 3 players
		while (currentPlayers.size() < 2){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		};
		
		while (keepPlaying) {
			//a game consists of playing 10 rounds
			while (roundCount <= 10) {
				//set gamePosition
				gamePosition = 1;
				//generating the acronym. Currently ever acronym for every round is 4 characters long
				acro = "";
				for (int i = 0; i < 4; i++) {
					acro = acro
							+ String.valueOf(Character.toChars((generator
									.nextInt(25)) + 65));
				}

				//send every player the acronym. TODO: change from sending the string to sending a ClientCommand with the string in it
				for (PlayerThread p : currentPlayers) {
					p.send(new ClientAcroCommand(acro, roundCount, 60));
				}

				System.out.println("The wait for answers has started. ACRO: "
						+ acro);
				// pause game thread for 60 seconds and just receive answers
				long start = System.currentTimeMillis();
				long end = start + 60 * 1000; // 60 seconds * 1000 ms/sec
				while (System.currentTimeMillis() < end) {}	;

				System.out
						.println("No longer receiving answers. Collecting and sending out answers.");
				ArrayList<String> answers = new ArrayList<String>();
				// traverse through all the players and get their answers. Store them in a list.
				for (PlayerThread p : currentPlayers) {
					answers.add(p.answer);
				}
				// traverse through all the players and send them the list of answers
				for (PlayerThread p : currentPlayers) {
					System.out.println("Sending "+p.username+" the answers");
					p.send(new ClientAnswerCommand(answers, roundCount, 30));
				}

				System.out.println("The wait for votes has started.");
				// pause game thread for 30 seconds and just receive votes
				start = System.currentTimeMillis();
				end = start + 30 * 1000; // 30 seconds * 1000 ms/sec
				while (System.currentTimeMillis() < end) {};

				System.out
						.println("No longer receiving votes. Collecting and sending out votes.");
				// traverse through all the players and get their votes. Add to players scores.
				for (PlayerThread p : currentPlayers) {
					for (PlayerThread p2 : currentPlayers) {
						if (p.vote.equals(p2.answer) && !p.vote.equals(""))
							p2.score++;
					}
				}

				//send out scores
				ArrayList<String> scores = new ArrayList<String>();
				for (PlayerThread p : currentPlayers) {
					scores.add(p.username + " - " + p.score);	
				}
				for (PlayerThread p : currentPlayers) {
					p.send(new ClientScoreCommand(scores, roundCount, 15));
				}
				

				start = System.currentTimeMillis();
				end = start + 15 * 1000; // 15 seconds * 1000 ms/sec
				while (System.currentTimeMillis() < end) {};
				
				roundCount++;
			}
			
			//after 10 rounds, reset scores and start again
			roundCount = 1;
			for (PlayerThread p : currentPlayers) {
				p.score = 0;
			}

		}

	}
	
	//close the room. The room will close at the end of the next round.
	public void closeRoom(){
		//remove all Players
		for (PlayerThread p : currentPlayers) {
			p.close();
		}
		//set Booleans
		keepPlaying = false;
		roundCount = 11;
	}

	// create a new PlayerThread and add it to the list of PlayerThreads
	synchronized private void establishNewPlayer(Socket s) {
		//handle if room is full (10 people is full)
		if (currentPlayers.size() == 10) {
			// TODO:send message too full
		} else {
			//generate new PlayerThread for new player and add thread to currentPlayers
			PlayerThread pt = new PlayerThread(s, this);
			currentPlayers.add(pt);
			System.out.println("Thread Added. Currentplayers size: "+currentPlayers.size());
			pt.start();
		}
	}

	// send to all currentPlayers a message that belongs in chat
	public void roomChatBroadcast(String msg) {
		System.out.println("Currentplayers size: "+currentPlayers.size());
		for (PlayerThread p : currentPlayers) {
			System.out.println("Broadcasting msg to "+p.username);
			p.send(new ClientChatCommand(msg));
		}
	}
	
	//remove player from the room
	public void removePlayer(String username){
		//find PlayerThread that contains the username of the player
		for (PlayerThread p : currentPlayers) {
			if (p.username.equals(username)){
				currentPlayers.remove(p);
				p.close();
				return;
			}
		}
	}

	// This thread perpetually waits for new players to enter the room and handles them when they do.
	class NewPlayerThread extends Thread {
		ServerSocket ss;

		public NewPlayerThread(int port) {
			try {
				ss = new ServerSocket(port);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}

		public void run() {
			try {
				System.out.println("Opening Socket - " + InetAddress.getLocalHost());
			} catch (UnknownHostException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			while (true) {
				try {
					//establish connection and create PlayerThread
					System.out.println("Waiting For Player");
					Socket s = ss.accept();
					System.out.println("Player Found");
					establishNewPlayer(s);
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}

			}
		}
	}

	//This thread represents the connection between a player and the room. each player thas their own thread.
	class PlayerThread extends Thread {
		// the socket where to listen/talk
		Socket socket;
		ObjectInputStream sInput;
		ObjectOutputStream sOutput;
		// the Username of the Client
		String username;
		// the only type of message a will receive
		Object cm;
		Room room;
		int score = 0;
		String vote = "";
		String answer = "";
		boolean playerKeepPlaying;

		// Constructor
		PlayerThread(Socket socket, Room room) {
			this.socket = socket;
			this.room = room;
			playerKeepPlaying = true;
			try {
				// create output first
				sOutput = new ObjectOutputStream(this.socket.getOutputStream());
				sInput = new ObjectInputStream(this.socket.getInputStream());
				username = (String)sInput.readObject();
			} catch (Exception e) {
				System.out
						.println("Exception creating new Input/output Streams: "
								+ e);
				return;
			}
		}

		public void run() {
			System.out.println(username + " has joined the game");
			roomChatBroadcast(username + " joined the game.");
			System.out.println("receiving ServerCommands from "+username);
			while (playerKeepPlaying) {
				//this thread continually receives ServerCommands from the player and executes them.
				//ServerCommands can consist of:
				//--entering something into chat
				//--changing the answer variable
				//--changing the vote variable
				//--logging out
				try {
					cm = sInput.readObject();
					if(cm instanceof ServerChatCommand){
						roomChatBroadcast(((ServerChatCommand) cm).getName()+": "+((ServerChatCommand) cm).getMsg());
					}else if(cm instanceof ServerAnswerCommand){
						System.out.println("answer command received");
						for (PlayerThread p : currentPlayers) {
							if(((ServerAnswerCommand) cm).getName().equals(p.username)){
								p.answer = ((ServerAnswerCommand) cm).getResponse();
								System.out.println(p.username + " just changed his answer to " + p.answer);
								break;
							}
						}
					}else if(cm instanceof ServerVoteCommand){
						System.out.println("vote command received");
						vote = ((ServerVoteCommand) cm).getVote();
					}
				} catch (Exception e) {
					System.out.println(username
							+ " Exception reading Streams: " + e);
					break;
				}
				
			}

			close();
		}

		// send an object to Player
		public int send(Object o) {
			try {
				sOutput.writeObject(o);
				return 0;
			} catch (IOException e) {
				return 1;
			}
		}

		// try to close everything
		private void close() {
			// try to close the connection
			try {
				if (sOutput != null)
					sOutput.close();
			} catch (Exception e) {
			}
			try {
				if (sInput != null)
					sInput.close();
			} catch (Exception e) {
			}
			;
			try {
				if (socket != null)
					socket.close();
			} catch (Exception e) {
			}
		}

	}

	// For testing
	public static void main(String[] args) {
		new Thread(new Room(280)).start();
	}

}
